<?php

/**
 * Represents a way of organizing a Form.
 *
 * To use this I recommend to overwrite the _form.php, _form_fieldset.php and _form_field.php
 * so they do not use the generator. Anyway, everything that the generator does in the forms can be
 * done here easily.
 *
 * @package    patrimonio
 * @subpackage form
 * @author     Matías A. Torres
 * @version    SVN: $Id: sfPropelFormBaseTemplate.php 9304 2008-05-27 03:49:32Z dwhittle $
 */
abstract class chuckiedBaseFormPropel extends sfFormPropel
{
  protected
    $ownName = null,
    $isEmbedded = false,
    $parentForm = null,
    $configuration = null,
    $dispatcher = null,
    $moduleName = null;

  /**
   *  Constructor that adds two parameters 'ownName' and 'parentForm'.
   *
   * @param $object
   * @param $options
   * @param $CSRFSecret
   * @param $ownName        The name given to this form in 'embedForm'. DEPRECATED. Use 'ownName' option.
   * @param $parentForm     The form that embedded this form. DEPRECATED. Use 'parentForm' option.
   *
   */
  public function __construct(BaseObject $object = null, $options = array(), $CSRFSecret = null, $ownName = null, $parentForm = null)
  {
    /** chuckyFormPropel __construct method **/
    $this->parentForm = isset($options['parentForm'])? $options['parentForm'] : $parentForm;
    $this->ownName    = isset($options['ownName'])? $options['ownName'] : $ownName;
    $this->dispatcher = sfContext::getInstance()->getEventDispatcher();
    $this->connectToFormSignals();

    if ($this->parentForm) $this->setIsEmbedded(true);

    if (isset($options['module']))
    {
      $this->moduleName = $options['module'];
    }

    $configurationClass = get_class($this).'Configuration';
    if (class_exists($configurationClass))
    {
      $this->configuration = new $configurationClass($this);
    }

    /** sfFormPropel __construct method **/
    $class = $this->getModelName();
    if (is_null($object))
    {
      $this->object = new $class();
    }
    else
    {
      if (!$object instanceof $class)
      {
        throw new sfException(sprintf('The "%s" form only accepts a "%s" object.', get_class($this), $class));
      }

      $this->object = $object;
      $this->isNew = $this->object->isNew();
    }

    /** sfForm __construct method **/
    $this->setDefaults(array());
    $this->options = $options;

    $this->validatorSchema = new sfValidatorSchema();
    $this->widgetSchema    = new sfWidgetFormSchema();
    $this->configureWidgetSchema();

    $this->errorSchema     = new sfValidatorErrorSchema($this->validatorSchema);

    $this->setup();
    $this->configureNameFormat();
    $this->dispatcher->notify(new sfEvent($this, 'chuckied_form.pre_configure', array('form' => $this)));
    $this->configure();
    $this->dispatcher->notify(new sfEvent($this, 'chuckied_form.pre_configure_optional_fields', array('form' => $this)));
    $this->configureOptionalFields();
    $this->dispatcher->notify(new sfEvent($this, 'chuckied_form.post_configure', array('form' => $this)));

    $this->addCSRFProtection($CSRFSecret);
    $this->resetFormFields();

    /** sfFormPropel __construct method **/
    $this->updateDefaultsFromObject();
  }

  protected function configureNameFormat()
  {
    if (!is_null($this->ownName))
    {
      $this->getWidgetSchema()->setNameFormat($this->ownName.'[%s]');
    }
  }

  protected function connectToFormSignals()
  {
  }

  public function configureOptionalFieldValidator($field, $related_name)
  {
    $this->getValidator($related_name)->setOption('required', !$this->getValueOfField($field, false));
  }

  public function configureOptionalField($field, $related_name)
  { 
    if (isset($this[$field]) && isset($this[$related_name]))
    {
      $this->getWidgetSchema()->moveField($field, 'before', $related_name);
      $this->getWidget($field)->setLabel(sfInflector::humanize($related_name).' not available');
      $this->configureOptionalFieldValidator($field, $related_name);
      if ($this->getValueOfField($field))
      {
        $this->disableInputs(array($related_name));
      }
      $onChange = trim($this->getWidget($field)->getAttribute('onChange', ''));
      if (empty($onChange))
      {
        $onChange = '';
      }
      elseif ($onChange[count($onChange)-1] != ';')
      {
        $onChange .= ';';
      }
      $onChange .= JavascriptInputHelper::getDisableEnableJsForCheckbox(array($this->getFullId($related_name)));
      $this->getWidget($field)->setAttribute('onChange', $onChange);
    }
  }

  public function configureOptionalFields()
  {
    foreach ($this as $field => $widget)
    {
      if (preg_match('/optional_field_empty(.*)/', $field, $matches))
      {
        if (!empty($matches[1]))
        {
          $this->configureOptionalField($field, substr($matches[1], 1, strlen($matches[1])-1));
        }
      }
    }
  }

  public function getModuleName()
  {
    if (is_null($this->moduleName) && sfContext::hasInstance())
    {
      return sfContext::getInstance()->getModuleName();
    }
    return $this->moduleName;
  }

  /**
   * Returns true if the options has been set/added. False otherwise.
   */
  public function hasOption($name)
  {
    return isset($this->options[$name]);
  }

  protected function configureWidgetSchema()
  {
    /** This chunk of widgetSchema lines sets the default formatters of the sfPropelRevisitedGenerator **/
    $this->widgetSchema->addFormFormatter(sfPropelRevisitedGeneratorConfiguration::getFormFormatterName(), new sfWidgetFormSchemaFormatterRevisited($this));
    $this->widgetSchema->addFormFormatter(sfPropelRevisitedGeneratorConfiguration::getEmbeddedFormFormatterName(), new sfWidgetFormSchemaFormatterRevisitedEmbedded($this));
    $this->widgetSchema->setFormFormatterName(sfPropelRevisitedGeneratorConfiguration::getFormFormatterName());
  }

  public function setWidgetSchema(sfWidgetFormSchema $widgetSchema)
  {
    $this->widgetSchema = $widgetSchema;
    $this->configureWidgetSchema();
    $this->resetFormFields();
  }

  /**
   * Sets a boolean indicating if this form is embedded
   *
   * @param boolean $value
   */
  public function setIsEmbedded($value)
  {
    $this->isEmbedded = $value;
  }

  /**
   * Gets a boolean that indicates if this form is embedded
   *
   * @return boolean
   */
  public function getIsEmbedded()
  {
    return $this->isEmbedded;
  }

  /**
   * Sets the decorator format of the embedded form and its new formatter since it is now embedded.
   */
  public function embedForm($name, sfForm $form, $decorator = null)
  {
    if ($form instanceOf chuckiedBaseFormPropel)
    {
      if (is_null($decorator))
      {
        $currentFormatterName = $this->widgetSchema->getFormFormatterName();
        $this->widgetSchema->setFormFormatterName(sfPropelRevisitedGeneratorConfiguration::getEmbeddedFormFormatterName());
        $decorator = $this->widgetSchema->getFormFormatter()->getDecoratorFormat();
        $this->widgetSchema->setFormFormatterName($currentFormatterName);
      }
      $form->getWidgetSchema()->setFormFormatterName(sfPropelRevisitedGeneratorConfiguration::getEmbeddedFormFormatterName());
      $form->setIsEmbedded(true);
    }
    parent::embedForm($name, $form, $decorator);
  }

  /**
   * Changes the default behavior. If this form is embedded, it will be render
   * using the 'embedded form formatter' specified in the app.yml. 
   * If this form is not embedded it will be rendered using the 'form formatter'
   * specified in the app.yml
   */
  public function render($attributes = array())
  {
    if ($this->isEmbedded)
    {
      return $this->renderUsing(sfPropelRevisitedGeneratorConfiguration::getEmbeddedFormFormatterName(), $attributes);
    }
    else
    {
      return $this->renderUsing(sfPropelRevisitedGeneratorConfiguration::getFormFormatterName(), $attributes);
    }
  }

  /**
   * Renders the widget schema using a specific form formatter
   *
   * @param string $formatterName The form formatter name
   * @param array  $attributes    An array of HTML attributes
   *
   * @return string The rendered widget schema
   */
  public function renderUsing($formatterName, $attributes = array())
  {
    $currentFormatterName = $this->widgetSchema->getFormFormatterName();

    $this->widgetSchema->setFormFormatterName($formatterName);
    
    $output = parent::render($attributes);

    $this->widgetSchema->setFormFormatterName($currentFormatterName);

    return $output;
  }

  /**
   * Sets fieldsets just like the generator.yml does. By default no fieldset is set (except NONE).
   */
  public function getFieldsets()
  {
    $fields = array();
    foreach ($this as $name => $field)
    {
      $fields[$name] = $name;
    }
    return array('NONE' => $fields);
  }

  /**
   * Unsets all fields except the fields in the parameter $fields
   */
  protected function unsetAllExcept($fields = array())
  {
    foreach (array_diff(array_keys($this->widgetSchema->getFields()), $fields) as $value)
    {
      unset($this[$value]);
    }
  }

  /**
   * Retrieves the complete name of the form (including parent forms)
   */
  public function getFullName($field = null)
  {
    $name = $this->getName();
    if (!is_null($this->parentForm))
    {
      $name = sprintf("%s[%s]", $this->parentForm->getFullName(), $this->getName());
    }

    if (!is_null($field))
    {
      $name = sprintf("%s[%s]", $name, $field);
    }
    return $name;
  }

  /**
   * Retrieves an array with (ParentForm0, ParentForm1, ..., ParentForm1, ThisForm)
   *
   * @return array
   */
  public function getFormChain()
  {
    $forms = array();
    if (!is_null($this->parentForm))
    {
      $parents = $this->parentForm->getFormChain();
      foreach ($parents as $p => $f)
      {
        $forms[$p] = $f;
      }
    }
    $forms[$this->getName()] = $this;
    return $forms;
  }

  /**
   * Retrieves the complete ID of the form (including parent forms)
   */
  public function getFullId($field = null)
  {
    $id = str_replace(array('[', ']'), array('_', ''), $this->getFullName($field));
    if (!empty($id) && $id[strlen($id)-1] == ']')
    {
      $id = array_slice($id, 0, strlen($id)-1);
    }
    return $id;
  }

  /**
   * Agarra el javascript de este formulario y todos sus hijos. Sé que el nombre está mal
   * pero es mejor llamar a este y no a 'getPostRenderJavascript' en los templates.
   */
  public function getChildPostRenderJavascripts()
  {
    $js = "";
    foreach ($this->getEmbeddedForms() as $name => $f)
    {
      if (method_exists($f, 'getChildPostRenderJavascripts'))
        $js .= $f->getChildPostRenderJavascripts();
    }
    if (method_exists($this, 'getPostRenderJavascript'))
      $js .= $this->getPostRenderJavascript();
    return $js;
  }

  /**
   * This functions must return javascript that should be executed inmediatly after
   * the form is executed
   */
  public function getPostRenderJavascript()
  {
    return '';
  }

  /**
   * This functions sets the javascripts that triggers the ajax requests
   */
  protected function setAjaxCallbacks()
  {
  }

  /**
   * This function set independent javascript for fields that needs some visual javascript interaction
   */
  protected function setJavascripts()
  {
  }

  /**
   * Do not override this function. Use 'configureUnsetFields' instead
   */
  protected function unsetFields()
  {
    foreach ($this->configureUnsetFields() as $field)
    {
      unset($this[$field]);
    }
  }

  /**
   * Configure widgets here
   */
  protected function configureWidgets()
  {
  }

  /**
   * Configure validators here
   */
  protected function configureValidators()
  {
  
  }

  /**
   * Configure embedded forms here
   */
  protected function configureEmbeddedForms()
  {
  }

  /**
   * Configure fields here
   */
  protected function configureFields()
  {
  }


  /**
   * Returns an array of field names to unset
   */
  protected function configureUnsetFields()
  {
    return array();
  }

  /**
   * DEPRECATED, use configureLabels
   * Set labels and help here
   */
  protected function setLabels()
  {
  }

  /**
   * Set labels and help here
   */
  protected function configureLabels()
  {
  }

  /**
   * This function disables inputs by adding a 'sf_admin_disabled_class' to the them.
   * Setting the attribute 'disabled/disable' to true does not work on IE6,
   * so this is the only way I found to disable an input.
   *
   * @param $names Field names of inputs to disable
   */
  protected function disableInputs($names)
  {
    foreach ($names as $name)
    {
      if (isset($this[$name]))
      {
        $classes = $this[$name]->getWidget()->getAttribute('class');
        if (is_null($classes))
        {
          $classes = 'sf_admin_disabled_input';
        }
        else
        {
          $classes .= ' sf_admin_disabled_input';
        }
        $this[$name]->getWidget()->setAttribute('class', $classes);
      }
      else
        throw new sfException("Field ".$name." does not exist");
    }
  }

  /**
   * Obtains the values from the request (first) or from the form's object (if the value is not in the request).
   *
   * Utility of this method: If a validation error occurs the object is not updated (it is updated in the doSave method)
   * so, if you want the user supplied value, you have to fetch it from the request.
   *
   * If the request method is POST or PUT, this method tries to fetch the value from the request 
   *    and if the desired value is not in the request, returns the $nos_set_value parameter.
   * If the request method is different, it fetches from the OBJECT.
   *
   * @param $fieldName the name of the form field.
   * @param mixed $not_set_value value to return if the method is POST or PUT and the desired value is not in the request.
   *
   * @return mixed
   */
  public function getValueOfField($fieldName, $not_set_value=null)
  {
    $formChain = $this->getFormChain();
    $formChainKeys = array_keys($formChain);

    $firstFormName = $formChainKeys[0];
    $firstForm     = $formChain[$firstFormName];

    unset($formChain[$firstFormName], $formChainKeys[0]);

    $request = sfContext::getInstance()->getRequest();
    if (($request->getMethod() == sfRequest::POST || $request->getMethod() == sfRequest::PUT) && !$request->isXmlHttpRequest())
    {
      $submitted_values = $request->getParameter($firstFormName);
      foreach ($formChain as $name => $f)
      {
        if (isset($submitted_values[$name]))
        {
          $submitted_values = $submitted_values[$name];
        }
      }
      return isset($submitted_values[$fieldName])?$submitted_values[$fieldName]:$not_set_value;
    }
    else
    {
      if (method_exists($this->getObject(), 'get'.sfInflector::classify($fieldName)))
      {
        $return_value = call_user_func(array($this->getObject(), 'get'.sfInflector::classify($fieldName)));
      }
      else
      {
        $return_value = $not_set_value;
      }

      return $return_value ? $return_value : $not_set_value;
    }
  }


  /**
   * Template method
   */
  public function configure()
  {
    $this->configureWidgets();
    $this->configureValidators();
    $this->configureEmbeddedForms();
    $this->configureFields();
    $this->configureLabels();
    $this->setLabels();
    $this->setJavascripts();
    $this->setAjaxCallbacks();
    $this->unsetFields();
  }

  public function setup()
  {

  }

  public function save($con = null)
  {
    $this->dispatcher->notify(new sfEvent($this, 'chuckied_form.pre_save_'.sfInflector::underscore($this->getModelName()), array('object' => $this->getObject())));
    $object = parent::save();
    $this->dispatcher->notify(new sfEvent($this, 'chuckied_form.post_save_'.sfInflector::underscore($this->getModelName()), array('object' => $object)));
    return $object;
  }

  public function getFieldNames()
  {
    $fieldNames = array();
    foreach ($this as $fieldName => $field)
    {
      $fieldNames[] = $fieldName;
    }
    return $fieldNames;
  }

  protected function getRenderizedFields()
  {
    $renderized = array();
    foreach ($this->getFieldsets() as $fieldset)
    {
      if (is_array($fieldset))
      {
        foreach ($fieldset as $field => $name)
        {
          $renderized[] = $name;
        }
      }
    }
    return $renderized;
  }


  public function getUnrenderizedFields()
  {
    $fields = $this->getFieldNames();
    $renderized = $this->getRenderizedFields();

    $unrenderized = array();
    foreach (array_diff($fields, $renderized) as $name)
    {
      if (!$this[$name]->isHidden()) $unrenderized[] = $name;
    }

    return $unrenderized;
  }

  public function getParentForm()
  {
    return $this->parentForm;
  }
}
